package jmine.tec.rpc.common.serialization;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import jmine.tec.rpc.common.serialization.json.JSONClassConverter;

import org.junit.Before;
import org.junit.Test;

import bancosys.tec.persist.Persistable;
import bancosys.tec.persist.dao.BeanNotFoundException;
import bancosys.tec.utils.date.Date;
import bancosys.tec.utils.date.Time;
import bancosys.tec.utils.date.Timestamp;

/**
 * Base conversion test
 * 
 * @author lundberg
 */
public class ASerializerShould {

    private Serializer serializer;

    /**
     * Initializes the serializer
     */
    @Before
    public void init() {
        this.serializer = SerializerBuilder.aSerializer().build();
    }

    /**
     * Tests string serialization
     */
    @Test
    public void serializeStrings() {
        this.testSerializationFor("testString");
    }

    /**
     * Tests string serialization
     */
    @Test
    public void serializeMultilineStrings() {
        this.testSerializationFor("test\n\tString");
    }

    /**
     * Tests string serialization
     */
    @Test
    public void serializeUTF8Strings() {
        this.testSerializationFor("àáãäãâ°£¹²³");
    }

    /**
     * Tests strange string serialization
     */
    @Test
    public void serializeStrangeStrings() {
        this.testSerializationFor("test\\\"\\[]{}String");
    }

    /**
     * Boolean TRUE serialization
     */
    @Test
    public void serializeBooleanTrue() {
        this.testSerializationFor(Boolean.TRUE);
    }

    /**
     * Boolean FALSE serialization
     */
    @Test
    public void serializeBooleanFalse() {
        this.testSerializationFor(Boolean.FALSE);
    }

    /**
     * BigDecimal serialization
     */
    @Test
    public void serializeBigDecimal() {
        this.testSerializationFor(new BigDecimal("42.123"));
    }

    /**
     * Null object serialization
     */
    @Test
    public void serializeNull() {
        this.testSerializationFor(null);
    }

    /**
     * Empty list serialization
     */
    @Test
    public void serializeEmptyList() {
        this.testSerializationFor(new ArrayList<Object>());
    }

    /**
     * String list serialization
     */
    @Test
    public void serializeStringList() {
        this.testSerializationFor(Arrays.asList(new String[]{ "a", "b", "abc" }));
    }

    /**
     * Map serialization
     */
    @Test
    public void serializeMap() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("a", BigDecimal.ONE);
        map.put("b", "1");
        this.testSerializationFor(map);
    }

    /**
     * Map serialization
     */
    @Test
    public void serializeEmptyMap() {
        this.testSerializationFor(Collections.emptyMap());
    }

    /**
     * Object serialization
     */
    @Test
    public void serializeObject() {
        this.testSerializationFor(MockBeanBuilder.aBean().build());
    }

    /**
     * Object serialization with null property
     */
    @Test
    public void serializeObjectWithNull() {
        this.testSerializationFor(MockBeanBuilder.aBean().withNumber(null).build());
    }

    /**
     * Object serialization with empty list property
     */
    @Test
    public void serializeObjectWithEmptyList() {
        this.testSerializationFor(MockBeanBuilder.aBean().withNumberList(new ArrayList<BigDecimal>()).build());
    }

    /**
     * Object serialization with circular reference
     */
    @Test
    public void serializeObjectWithCircularReferences() {
        MockBean mock = MockBeanBuilder.aBean().build();
        mock.getObjectList().add(mock);
        MockBean deserialized = (MockBean) this.serializeAndDeserialize(mock);
        assertThat(deserialized == deserialized.getObjectList().get(0), is(true));
    }

    /**
     * Date serialization
     */
    @Test
    public void serializeDate() {
        this.testSerializationFor(new Date());
    }

    /**
     * Date serialization
     */
    @Test
    public void serializeTimestamp() {
        this.testSerializationFor(new Timestamp());
    }

    /**
     * Date serialization
     */
    @Test
    public void serializeTime() {
        this.testSerializationFor(new Time());
    }

    /**
     * Class serialization
     */
    @Test
    public void serializeClass() {
        this.testSerializationFor(ASerializerShould.class);
    }

    /**
     * Class serialization
     */
    @Test
    public void serializeObjectArrays() {
        this.testSerializationFor(new Object[]{ "1", BigDecimal.TEN, Boolean.TRUE });
    }

    /**
     * Class serialization
     */
    @Test
    public void serializeClassArrays() {
        this.testSerializationFor(new Class[]{ ASerializerShould.class, SerializerBuilder.class, JSONClassConverter.class });
    }

    /**
     * Array class serialization
     */
    @Test
    public void serializeArrayClasses() {
        this.testSerializationFor(Integer[].class);
    }

    /**
     * Array class serialization
     */
    @Test
    public void serializeMultipleDimentionArrayClasses() {
        this.testSerializationFor(Integer[][][][].class);
    }

    /**
     * Array class serialization
     */
    @Test
    public void serializeIntValuedEnum() {
        MockIntValueEnum testObject = new MockIntValueEnum("teste", 1);
        this.testSerializationFor(testObject);
    }

    /**
     * Array class serialization
     */
    @Test
    public void serializeStringValuedEnum() {
        MockStringValueEnum testObject = new MockStringValueEnum("teste", "um");
        this.testSerializationFor(testObject);
    }

    /**
     * Class serialization
     */
    @Test
    public void serializeAnnotations() {
        this.testSerializationFor(MockAnnotation.VALUEA);
    }

    /**
     * Class serialization
     */
    @Test
    public void serializeLongValues() {
        this.testSerializationFor(1L);
    }

    /**
     * Class serialization
     */
    @Test
    public void serializeIntegersValues() {
        this.testSerializationFor(1);
    }

    /**
     * Class serialization
     */
    @Test
    public void serializeNoni18nExceptions() {
        NullPointerException exception = new NullPointerException("Sample NPE.");
        NullPointerException result = (NullPointerException) this.serializeAndDeserialize(exception);
        assertThat(result.getMessage(), is(equalTo(exception.getMessage())));
    }

    /**
     * Class serialization
     */
    @Test
    public void serializeBeanNotFoundExceptions() {
        BeanNotFoundException exception = new BeanNotFoundException(Persistable.class);
        BeanNotFoundException result = (BeanNotFoundException) this.serializeAndDeserialize(exception);
        assertThat(result.getMessage(), is(equalTo(exception.getMessage())));
    }

    /**
     * Tests serialization of a given object
     * 
     * @param original original object
     */
    private void testSerializationFor(Object original) {
        Object deserialized = this.serializeAndDeserialize(original);
        assertThat("Unmarshalled an original do not match.", deserialized, is(equalTo((original))));
    }

    /**
     * @param original original
     * @return result
     */
    private Object serializeAndDeserialize(Object original) {
        try {
            InputStream stream = this.serializer.serialize(original);
            return this.serializer.deserialize(stream);
        } catch (IOException e) {
            fail(e.getMessage());
            throw new IllegalStateException();
        } catch (ParseException e) {
            fail(e.getMessage());
            throw new IllegalStateException();
        }
    }
}